小编典典

单线程非阻塞 IO 模型如何在 Node.js 中工作

all

我不是 Node 程序员,但我对 单线程非阻塞 IO 模型的 工作原理很感兴趣。在我阅读了理解-the-node-js-event-
loop
的文章后,我真的很困惑。它为模型提供了一个示例:

c.query(
   'SELECT SLEEP(20);',
   function (err, results, fields) {
     if (err) {
       throw err;
     }
     res.writeHead(200, {'Content-Type': 'text/html'});
     res.end('<html><head><title>Hello</title></head><body><h1>Return from async DB query</h1></body></html>');
     c.end();
    }
);

问:
当有两个请求A(先来)和B,因为只有一个线程,服务器端程序会先处理请求A:做SQL查询是休眠语句,等待I/O。并且程序一直处于I/O等待状态,无法执行渲染网页的代码。程序会在等待期间切换到请求
B 吗?在我看来,由于单线程模型,没有办法将一个请求从另一个请求切换。但是示例代码的标题表明 除了您的代码之外的所有内容都是并行运行的

(PS我不确定我是否误解了代码,因为我从未使用过Node。)Node如何在等待期间将A切换到B?并且能简单的解释一下Node
的单线程非阻塞IO模型吗? 如果您能帮助我,我将不胜感激。:)


阅读 270

收藏
2022-04-04

共1个答案

小编典典

Node.js 基于libuv 构建,这是一个跨平台库,它为支持的操作系统(至少是
Unix、OS X 和 Windows)提供的异步(非阻塞)输入/输出抽象 apis/syscall。

异步 IO

在此编程模型中,文件系统管理的设备和资源(套接字、文件系统等)上的打开/读/写操作 不会阻塞调用线程 (如在典型的同步类 c
模型中),只需标记当新数据或事件可用时通知进程(在内核/操作系统级数据结构中)。对于类似 Web
服务器的应用程序,该进程负责确定通知事件属于哪个请求/上下文,并从那里继续处理请求。请注意,这必然意味着您将位于与向操作系统发起请求的堆栈帧不同的堆栈帧上,因为后者必须屈服于进程的调度程序,以便单线程进程处理新事件。

我所描述的模型的问题在于它对程序员来说并不熟悉且难以推理,因为它本质上是非顺序的。“您需要在函数 A 中提出请求,并在另一个函数中处理结果,在该函数中,来自
A 的本地人通常不可用。”

Node的模型(继续传递样式和事件循环)

Node 利用 javascript 的语言特性解决了这个问题,通过引导程序员采用某种编程风格,使这个模型看起来更加同步。每个请求 IO
的函数都有一个类似的签名,function (... parameters ..., callback)并且需要给一个回调,当请求的操作完成时将调用该回调(请记住,大部分时间都花在等待操作系统发出完成信号 -
时间可以是做其他工作)。Javascript
对闭包的支持允许您使用在回调主体内的外部(调用)函数中定义的变量——这允许在节点运行时独立调用的不同函数之间保持状态。另请参阅继续传递样式

此外,在调用产生 IO 操作的函数后,调用函数通常会return控制节点的 事件循环
。此循环将调用计划执行的下一个回调或函数(很可能是因为操作系统通知了相应的事件)——这允许并发处理多个请求。

您可以将节点的事件循环视为 有点类似于内核的调度 程序:一旦其挂起的 IO 完成,内核将安排执行阻塞线程,而节点将在相应事件发生时安排回调。

高并发,无并行

最后一句话,“除了你的代码之外,一切都并行运行”这句话很好地捕捉到了这一点,即节点允许你的代码通过多路复用和排序你的所有 js来同时处理来自
数十万个打开套接字的 请求。单个执行流中的逻辑(即使说“一切都并行运行”在这里可能不正确 - 请参阅并发与并行 -
有什么区别?
)。这对于 webapp
服务器非常有效,因为大部分时间实际上都花在等待网络或磁盘(数据库/套接字)上,并且逻辑并不是真正的 CPU 密集型 - 也就是说: 这适用于 IO-
bound 工作负载

2022-04-04